#include "surface_utils.h"
#include "matrix4x4.h"
#include <cassert>

unsigned int SurfaceUtils::revolutionStep = 16;

void SurfaceUtils::calculateRevolutionSurface(const Point& center,const Point& axis,const std::vector<Point>& curvePoints,Surface& surface){
	Matrix4x4 t1(-center.x1,-center.x2,-center.x3);
	Matrix4x4 t1i(center.x1,center.x2,center.x3);
	for(unsigned int c=0;c<revolutionStep;c++){
		for(unsigned int w=0;w<curvePoints.size();w++){
			Matrix4x4 m;
			// Roto el punto un angulo dado respecto el centro y eje indicado
			Matrix4x4 r1(2*PI*c/(revolutionStep-1),axis);
			m*= t1;
			m*= r1;
			m*= t1i;
			Point p = m*(curvePoints[w]);

			surface.points.push_back(p.x1);
			surface.points.push_back(p.x2);
			surface.points.push_back(p.x3);

			Point normal  = (p-center);
			normal = normal*(1/normal.mod());
			surface.normals.push_back((normal).x1);
			surface.normals.push_back((normal).x2);
			surface.normals.push_back((normal).x3);
		}
	}

	surface.texture.push_back(0.0f);
	surface.texture.push_back(0.0f);

	surface.texture.push_back(0.0f);
	surface.texture.push_back(1.0f);

	surface.texture.push_back(1.0f);
	surface.texture.push_back(0.0f);

	surface.texture.push_back(1.0f);
	surface.texture.push_back(1.0f);
}

void SurfaceUtils::calculateRevolutionSurfaceIndices(const std::vector<Point>& curvePoints,Surface& surface){
    calculateSurfaceIndices(revolutionStep,curvePoints.size(),surface);
}

void SurfaceUtils::calculateSurfaceIndices(unsigned int count,unsigned int size,Surface& surface){
    // No se calcula un strip sino triangulos
    for(unsigned int i=1; i<count;i++){
		for(unsigned int j=0;j<(size-1);j++){
			//////////// T1
			// v
			surface.indices.push_back((i-1)*size+j);
			// vt
			surface.indices.push_back(0);
			// vn
			surface.indices.push_back((i-1)*size+j);

			// v
			surface.indices.push_back(i*size+j);
			// vt
			surface.indices.push_back(1);
			// vn
			surface.indices.push_back(i*size+j);

			// v
			surface.indices.push_back((i-1)*size+(j+1));
			// vt
			surface.indices.push_back(2);
			// vn
			surface.indices.push_back((i-1)*size+(j+1));

			//////////// T2
			// v
			surface.indices.push_back(i*size+j);
			// vt
			surface.indices.push_back(1);
			// vn
			surface.indices.push_back(i*size+j);

			// v
			surface.indices.push_back(i*size+(j+1));
			// vt
			surface.indices.push_back(3);
			// vn
			surface.indices.push_back(i*size+(j+1));

			// v
			surface.indices.push_back((i-1)*size+(j+1));
			// vt
			surface.indices.push_back(2);
			// vn
			surface.indices.push_back(i*size+(j+1));
		}
    }

	// me interesa como conjunto no contar v,vt,vt contados como 3 indices
    surface.groupsSizes.push_back(surface.indices.size()/9);
}

void SurfaceUtils::calculateSurfaceIndices2(unsigned int count,unsigned int size,Surface& surface){
    // No se calcula un strip sino triangulos
    for(unsigned int i=1; i<count;i++){
		for(unsigned int j=0;j<(size-1);j++){
			//////////// T1
			// v
			surface.indices.push_back((i-1)*size+j);
			// vt
			surface.indices.push_back((i-1)*size+j);
			// vn
			surface.indices.push_back((i-1)*size+j);

			// v
			surface.indices.push_back(i*size+j);
			// vt
			surface.indices.push_back(i*size+j);
			// vn
			surface.indices.push_back(i*size+j);

			// v
			surface.indices.push_back((i-1)*size+(j+1));
			// vt
			surface.indices.push_back((i-1)*size+(j+1));
			// vn
			surface.indices.push_back((i-1)*size+(j+1));

			//////////// T2
			// v
			surface.indices.push_back(i*size+(j+1));
			// vt
			surface.indices.push_back(i*size+(j+1));
			// vn
			surface.indices.push_back(i*size+(j+1));

			// v
			surface.indices.push_back((i-1)*size+(j+1));
			// vt
			surface.indices.push_back((i-1)*size+(j+1));
			// vn
			surface.indices.push_back((i-1)*size+(j+1));

			// v
			surface.indices.push_back(i*size+j);
			// vt
			surface.indices.push_back(i*size+j);
			// vn
			surface.indices.push_back(i*size+j);
		}
    }

	// me interesa como conjunto no contar v,vt,vt contados como 3 indices
    surface.groupsSizes.push_back(surface.indices.size()/9);
}

void SurfaceUtils::calculateTiledSurface(Point normal,Point center,unsigned int rows,unsigned int cols,float colSize,float rowSize,Surface& surface){
	if(normal.mod()!=1.0f)
		normal = normal*(1/normal.mod());

	float xytheta = atan2(normal.x1,normal.x2);
	float yztheta = atan2(sqrt(pow(normal.x1,2)+pow(normal.x2,2)),normal.x3);

	// Centramos y alineamos el plano con xy
	// Calculamos todos los puntos y los llevamos a su posicion original
	Matrix4x4 t1(-center.x1,-center.x2,-center.x3);
	Matrix4x4 r1(xytheta,Point(0,0,1));
	Matrix4x4 r2(yztheta,Point(1,0,0));
	// Empiezo a crear los puntos desde alguna esquina(z=0, ya estoy en xy)
	Matrix4x4 t2(-(rows*rowSize)/2,-(cols*colSize)/2,0);
	// Deshago las transformaciones
	Matrix4x4 r3(-yztheta,Point(1,0,0));
	Matrix4x4 r4(-xytheta,Point(0,0,1));
	Matrix4x4 t3(center.x1,center.x2,center.x3);

	// col,row es referido a la cantidad de vertices no de tiles
	for(unsigned int c=0;c<cols;c++){
		for(unsigned int r=0;r<rows;r++){
			Matrix4x4 m;
			// row = x , col = y
			Matrix4x4 t4(r*rowSize,c*colSize,0);
			m*= t1;
			m*= r1;
			m*= r2;
			m*= t2;
			m*= t4;
			m*= r3;
			m*= r4;
			m*= t3;
			Point p = m*center;

			surface.points.push_back(p.x1);
			surface.points.push_back(p.x2);
			surface.points.push_back(p.x3);

			surface.normals.push_back((normal).x1);
			surface.normals.push_back((normal).x2);
			surface.normals.push_back((normal).x3);
		}
	}

	surface.texture.push_back(0.0f);
	surface.texture.push_back(0.0f);

	surface.texture.push_back(0.0f);
	surface.texture.push_back(1.0f);

	surface.texture.push_back(1.0f);
	surface.texture.push_back(0.0f);

	surface.texture.push_back(1.0f);
	surface.texture.push_back(1.0f);
}

void SurfaceUtils::calculateTiledSurfaceIndices(unsigned int cols,unsigned int rows,Surface& surface){
    calculateSurfaceIndices(cols,rows,surface);
}

void SurfaceUtils::calculateTiledSurfaceHeightMapIndices(unsigned int rows,unsigned int cols,Surface& surface){
	calculateSurfaceIndices2(cols,rows,surface);
}

void SurfaceUtils::surfaceToObj(const Surface& surface,std::ostream& out,const std::string& mtlName){
	out << "# Archivo autogenerado por surfaceUtils v0.1\n\n";
	out << "mtllib " << mtlName << "\n";
	out << "g Object01\n";


	out << "# " << surface.points.size()/3 << " vertices\n";
	for(unsigned int c=0;c<surface.points.size();c+=3){
		out << "v " 	<< surface.points[c]
						<< " " << surface.points[c+1]
						<< " " << surface.points[c+2] << "\n";
	}
	out << std::endl;

	out << "# " << surface.normals.size()/3 << " normales\n";
	for(unsigned int c=0;c<surface.normals.size();c+=3){
		out << "vn " 	<< surface.normals[c]
						<< " " << surface.normals[c+1]
						<< " " << surface.normals[c+2] << "\n";
	}
	out << std::endl;

	out << "# " << surface.texture.size()/2 << " coordenadas textura\n";
	for(unsigned int c=0;c<surface.texture.size();c+=2){
		out << "vt " << surface.texture[c] << " " << surface.texture[c+1] << "\n";
	}
	out << std::endl;

	std::vector<unsigned int>::const_iterator groups = surface.groupsSizes.begin();
	bool writeMaterial=true;
	unsigned int w=0;
	unsigned int groupCount = 0;
	for(unsigned int c=0;c<surface.indices.size();c+=9,w++){
		if(writeMaterial){
			out << "usemtl " << "Material" << groupCount << "\n";
			writeMaterial=false;
		}

		// Los indices de un obj empiezan en 1
		out << "f " << surface.indices[c]+1 << "/" << surface.indices[c+1]+1 << "/" << surface.indices[c+2]+1;
		out << " " << surface.indices[c+3]+1 << "/" << surface.indices[c+4]+1 << "/" << surface.indices[c+5]+1;
		out << " " << surface.indices[c+6]+1 << "/" << surface.indices[c+7]+1 << "/" << surface.indices[c+8]+1 << "\n";

		if(w==(*groups)){
			groups++;
			w=0;
			groupCount++;
			writeMaterial=true;
		}
	}
	out << "# " << surface.indices.size()/9 << " triangulos\n";
	out << std::endl;
}

void SurfaceUtils::calculateTiledSurfaceHeightMap(Point normal,Point center,unsigned int rows,unsigned int cols,float colSize,float rowSize,const Texture& heightMap,float scaleHeightFactor,float zeroLevel,Surface& surface){
	if(normal.mod()!=1.0f)
		normal = normal*(1/normal.mod());

	float xytheta = atan2(normal.x1,normal.x2);
	float yztheta = atan2(sqrt(pow(normal.x1,2)+pow(normal.x2,2)),normal.x3);

	// Centramos y alineamos el plano con xy
	// Calculamos todos los puntos y los llevamos a su posicion original
	Matrix4x4 t1(-center.x1,-center.x2,-center.x3);
	Matrix4x4 r1(xytheta,Point(0,0,1));
	Matrix4x4 r2(yztheta,Point(1,0,0));
	// Empiezo a crear los puntos desde alguna esquina(z=0, ya estoy en xy)
	Matrix4x4 t2(-(rows*rowSize)/2,-(cols*colSize)/2,0);
	// Deshago las transformaciones
	Matrix4x4 r3(-yztheta,Point(1,0,0));
	Matrix4x4 r4(-xytheta,Point(0,0,1));
	Matrix4x4 t3(center.x1,center.x2,center.x3);

	float tystep = 1.0f/(float)rows;
	float txstep = 1.0f/(float)cols;
	// col,row es referido a la cantidad de vertices no de tiles
	for(unsigned int c=0;c<cols;c++){
		for(unsigned int r=0;r<rows;r++){
			Matrix4x4 m;
			// se supone que el tamanio del mapa y del archivo son identicos, no se chequea!
			float z = ( ((float)heightMap.data()[c + r*cols]-zeroLevel)*scaleHeightFactor);
			Matrix4x4 t4(r*rowSize,c*colSize,z);
			m*= t1;
			m*= r1;
			m*= r2;
			m*= t2;
			m*= t4;
			m*= r3;
			m*= r4;
			m*= t3;
			Point p = m*center;

			surface.points.push_back(p.x1);
			surface.points.push_back(p.x2);
			surface.points.push_back(p.x3);

			surface.normals.push_back(0.0f);
			surface.normals.push_back(0.0f);
			surface.normals.push_back(0.0f);


			surface.texture.push_back(.0f + txstep*c);
			surface.texture.push_back(.0f + tystep*r);
		}
	}
}

void SurfaceUtils::calculateTiledSurfaceHeightMapNormals(Surface& surface){
	// La normal de cada vertice se promedia con la normal de cada uno
	// de los triangulos a los que pertenece
	for(unsigned int i=0;i<surface.indices.size();i+=9){
		Point v1 = 	Point(	surface.points[(surface.indices[i])*3],
							surface.points[((surface.indices[i])*3) + 1],
							surface.points[((surface.indices[i])*3) + 2]);

		Point v2 = 	Point(	surface.points[(surface.indices[i+3])*3],
							surface.points[((surface.indices[i+3])*3) + 1],
							surface.points[((surface.indices[i+3])*3) + 2]);

		Point v3 = 	Point(	surface.points[(surface.indices[i+6])*3],
							surface.points[((surface.indices[i+6])*3) + 1],
							surface.points[((surface.indices[i+6])*3) + 2]);

		Point normal;
		normal = (v3-v1).cross(v2-v1);

		for(unsigned int w=0;w<3;w++){
			Point oldNormal = 	Point(	surface.normals[(surface.indices[(i+2)+(3*w)])*3],
										surface.normals[((surface.indices[(i+2)+(3*w)])*3) + 1],
										surface.normals[((surface.indices[(i+2)+(3*w)])*3) + 2]);


			Point newNormal = normal+oldNormal;
			surface.normals[(surface.indices[(i+2)+(3*w)])*3]		= newNormal.x1;
			surface.normals[((surface.indices[(i+2)+(3*w)])*3)+1]	= newNormal.x2;
			surface.normals[((surface.indices[(i+2)+(3*w)])*3)+2] 	= newNormal.x3;
		}
	}

	for(unsigned int c=0;c<surface.normals.size();c+=3){
		Point norm(surface.normals[c],surface.normals[c+1],surface.normals[c+2]);
		norm *= (1.0f/norm.mod());
		surface.normals[c] = norm.x1;
		surface.normals[c+1] = norm.x2;
		surface.normals[c+2] = norm.x3;
	}
}

void SurfaceUtils::generateHeightMapTexture(const std::vector<const Texture*>& textures,Texture& result,const Texture& heightMap,const Range& range){
	// Todas las texturas DEBEN tener el mismo tamanio que el height map
	// getRange debe devolver valores entre (0.0f-1.0f) y la suma de todos los valores debe dar 1.0f
	result.ctype(Texture::RGB);
	result.height(heightMap.height());
	result.width(heightMap.width());
	result.name("result.png");
	result.data(new unsigned char[heightMap.width()*heightMap.height()*3]);

	// Para cada pixel del height map se calcula el porcentaje de RGB que se necesita
	// de las n texturas disponibles.
	for(unsigned int h=0;h<heightMap.height();h++){
		for(unsigned int w=0;w<heightMap.width();w++){
			std::vector<float> ranges;
			unsigned char val = heightMap.data()[w + h*heightMap.width()];

			range.getRange(val,ranges);

			unsigned char rgb_pixel[3] = {0,0,0};
			// Promedio en funcion de los porcentajes
			for(unsigned int c=0;c<textures.size();c++){
				rgb_pixel[0] += (unsigned char)floor(ranges[c]*(float)textures[c]->data()[(w + h*heightMap.width())*3]);
				rgb_pixel[1] += (unsigned char)floor(ranges[c]*(float)textures[c]->data()[((w + h*heightMap.width())*3) + 1]);
				rgb_pixel[2] += (unsigned char)floor(ranges[c]*(float)textures[c]->data()[((w + h*heightMap.width())*3) + 2]);
			}

			// Color del pixel final
			result.data()[(w + h*heightMap.width())*3] = rgb_pixel[0];
			result.data()[((w + h*heightMap.width())*3) +1] = rgb_pixel[1];
			result.data()[((w + h*heightMap.width())*3) + 2] = rgb_pixel[2];
		}
	}
}
